package per.qiao.simplerest.restresolver;

import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.StringUtils;
import per.qiao.simplerest.anno.*;
import per.qiao.simplerest.restargument.RestParameter;
import per.qiao.simplerest.restenum.ParameterStyle;
import per.qiao.simplerest.restenum.RestMethod;
import per.qiao.simplerest.util.RestServiceUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;

/**
 * Company: 上海数惠系统技术有限公司
 * Department: 数据中心
 * Date: 2020-05-29 17:06
 *
 * @Author: qyp
 * Email: qiaoyp@dist.com.cn
 * Desc:
 */
public class DefaultRestMethodResolver implements RestMethodResolver {

    private final Class[] suportAnnos = {RestMapping.class, RestGetMapping.class,
            RestPostMapping.class, RestDeleteMapping.class, RestPutMapping.class};


    @Override
    public boolean supports(Method method) {
        for (Class annotation : suportAnnos) {
            if (AnnotatedElementUtils.hasAnnotation(method, annotation)) {
                return true;
            }
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *
     * @param method
     * @return
     */
    @Override
    public RestInvokerMethod resolverMethod(Method method) {

        RestInvokerMethod restInvokerMethods = createMethod(method);
        return restInvokerMethods;
    }

    private RestInvokerMethod createMethod(Method method) {

        Class<?> declaringClass = method.getDeclaringClass();
        RestMapping classAnnotation = declaringClass.getAnnotation(RestMapping.class);

        String methodAnnValue = null;
//        Parameter[] parameters = method.getParameters();
        DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discover.getParameterNames(method);
        Parameter[] parameters = method.getParameters();

        Class<?> returnType = method.getReturnType();
        Type genericReturnType = RestServiceUtils.getGenericType(method);

        RestMethod restMethod = RestMethod.GET;
        if (AnnotatedElementUtils.hasAnnotation(method, RestGetMapping.class)) {
            RestGetMapping restGetMapping = method.getAnnotation(RestGetMapping.class);
            methodAnnValue = restGetMapping.value();
            // int parameterCount = method.getParameterCount();
            restMethod = RestMethod.GET;
        } else if (AnnotatedElementUtils.hasAnnotation(method, RestPostMapping.class)) {
            RestPostMapping restMapping = method.getAnnotation(RestPostMapping.class);
            methodAnnValue = restMapping.value();
            restMethod = RestMethod.POST;
        } else if (AnnotatedElementUtils.hasAnnotation(method, RestDeleteMapping.class)) {
            RestDeleteMapping restMapping = method.getAnnotation(RestDeleteMapping.class);
            methodAnnValue = restMapping.value();
            restMethod = RestMethod.DELETE;
        } else if (AnnotatedElementUtils.hasAnnotation(method, RestPutMapping.class)) {
            RestPutMapping restMapping = method.getAnnotation(RestPutMapping.class);
            methodAnnValue = restMapping.value();
            restMethod = RestMethod.PUT;
        }

        String base = null;
        if (classAnnotation != null) {
            base = classAnnotation.base();
        }


        return buildMethod(base, mergeAnno(classAnnotation, methodAnnValue), restMethod,
                getRestParameter(parameterNames, parameters), returnType, genericReturnType);
    }


    private RestParameter[] getRestParameter(String[] parameterNames, Parameter[] parameters) {
        if (parameters == null) {
            return null;
        }
        RestParameter[] restParameters = new RestParameter[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            RestParameter restParameter = new RestParameter();
            if (AnnotatedElementUtils.hasAnnotation(parameter, RestPathVariable.class)) {
                RestPathVariable annotation = parameter.getAnnotation(RestPathVariable.class);
                String name = annotation.name();
                if (StringUtils.hasText(annotation.value())) {
                    restParameter.setAlias(name);
                } else if (StringUtils.hasText(name)) {
                    restParameter.setAlias(name);
                }
                restParameter.setParameterStyle(ParameterStyle.RESTFUL);
            } else if (AnnotatedElementUtils.hasAnnotation(parameter, RestRequestBody.class)) {
                restParameter.setParameterStyle(ParameterStyle.REQUESTBODY);
                restParameter.setName(parameterNames[i]);
            } else {
                restParameter.setParameterStyle(ParameterStyle.REQUESTPARAM);
                restParameter.setName(parameterNames[i]);
            }
            restParameters[i] = restParameter;
        }
        return restParameters;
    }

    /**
     * 构建方法对象
     * @param base              protocol + ip + port
     * @param uri               uri
     * @param restMethod        请求方式
     * @param restParameter     请求参数
     * @param returnType        返回类型
     * @return
     */
    private RestInvokerMethod buildMethod(String base, String uri, RestMethod restMethod, RestParameter[] restParameter,
                                          Class<?> returnType, Type genericReturnType) {
        return RestInvokerMethod.createBuild()
                .base(base)
                .uri(uri)
                .restMethod(restMethod)
                .restParameter(restParameter)
                .returnType(returnType)
                .genericReturnType(genericReturnType)
                .build();

    }

    /**
     * 将类上@RestMapping的value和方法上的组合起来
     * @param classAnno
     * @param methodAnnoValue
     * @return
     */
    private String mergeAnno(RestMapping classAnno, String methodAnnoValue) {
        if (classAnno == null || "".equals(classAnno.value().trim())) {
            return methodAnnoValue;
        }
        String classAnnoValue = classAnno.value();
        if (classAnnoValue.endsWith("/")) {
            classAnnoValue = classAnnoValue.substring(0, classAnnoValue.length() - 2);
        }
        if (!methodAnnoValue.startsWith("/")) {
            methodAnnoValue += "/";
        }
        return classAnnoValue + methodAnnoValue;
    }
}
